#include "Chord.h"


using namespace std;

Chord::Chord(int key,short port, int numero) {
	char ip[] = "127.0.0.1";
	connected = 0;
	this->numero = numero;
	this->key = key;
	this->myPort = port;
	this->myIP = ipCharToInt(ip);
	nexts = (CHnext **)malloc(sizeof(CHnext) * (int)MAX_NEXTS);//reserva a memória para os nós vizinhos
	memset(nexts,0,sizeof(CHnext)*(int)MAX_NEXTS);//limpa a memoria (coloca tudo em zero)
	this->terminar = 0;

	pthread_mutex_init(&this->semFind,NULL);
	pthread_mutex_init(&this->semDiv,NULL);
}

Chord::~Chord() {
	free(nexts);
	pthread_mutex_destroy(&this->semFind);
	pthread_mutex_destroy(&this->semDiv);
	
	printf("Chord morrendo, key = %d, numberOfKeys = %d\n",this->key,this->numberOfKeys);
	//delete semFind;
	//delete semDiv;
	//destroySem(semFind);
	//destroySem(semDiv);
}

/**
 * Converte um endereço IP em uma chave
 * @return unsigned int
 * @param	key Chave que deseja procurar na rede
 */
CHnext* Chord::findMachine (int key ) {
	//metodo foi simplificado e colocado em sendFindMachine (o metodo de busca, será utilizado em mais de um local do código
	CHnext *node;
	

	node = (CHnext *)malloc(sizeof(CHnext));
	if (node) {
		printf("iniciando busca\n");
		node->ip = myIP;
		node->port = myPort;

		CHnext* tmpNo;
		tmpNo = this->nextOfKey(key);
		if (tmpNo) {
			printf("%d: enviando busca para porta = %d\n",this->numero,tmpNo->port);
		} else printf("erro ao buscar proximo\n");
		sendFindMachine(key,node,tmpNo);
		free(node);
		//enterCR(semFind);
		//semFind->enterCR();
		pthread_mutex_lock(&this->semFind);
		return this->tmpNode;
	}
	return NULL;
}

void Chord::sendFindMachine(int key,CHnext *nodeFind,CHnext *node) {
	if (node && nodeFind) {
		char* buffer;
		char* tmp;
		
		buffer = (char *)malloc(sizeof(char) * 8);
		if (buffer) {
			tmp = buffer;

			memcpy(tmp,&nodeFind->ip,4);
			
			tmp = tmp + 4;
			memcpy(tmp,&nodeFind->port,2);
			tmp = tmp + 2;
			memcpy(tmp,&this->key,4);


			//supoe que a tabela de vizinhos esteja devidamente preenchida
			send(COP_FIND,buffer,10,node);
		}
	}
}


/**
 * Mapeia um nome de recurso em uma chave
 * @return unsigned int
 * @param	ipAddress Endereço IP
 */
int Chord::machineToKey (char* ipAddress ) {
	char tmp;
	int h = 0;
	int t = strlen(ipAddress);
	for (int i = 0;i < t;i++) {
		tmp = ipAddress[i];
		h += tmp;
	}
	return h;
}


/**
 * Connecta a uma rede Chord, o endereço passado é de um nó pertencente a essa
 * rede
 * @return unsigned int
 * @param	resource Nome do recurso que deverá ser mapeado
 */
int Chord::resourceToKey (char* resource ) {
	return this->machineToKey(resource);
}

void Chord::makeNexts() {
	int k;
	for (int i = 0;i < MAX_NEXTS;i++) {
		printf("testando\n");
		k = (key + (int)pow(2,i)) % MAX_NODE;
		printf("teste2\n");
		if (this->nexts[i]) {
			printf("teste3\n");
			free(this->nexts[i]);
			this->nexts[i] = NULL;
			printf("teste4\n");
		}
		printf("teste5\n");
		this->nexts[i] = findMachine(k);
		printf("criando i = %d, max = %f\n",i,MAX_NEXTS);
	}
}

void Chord::connectToNetwork (char *nodeAddress, short port) {
	if (this->connected) {
		this->disconnect();
	}

	int ip = ipCharToInt(nodeAddress);
	
	//atualiza a tabela, informando quem é o proximo nó (considera o ip informando como sendo o proximo no)
	CHnext* node;
	node = (CHnext *)malloc(sizeof(CHnext));

	if (node) {
		node->ip = ip;
		node->port = port;
		//this->nexts[0] = node;
		this->prior = node;
		int k;
		CHnext* n;	
		
		
		for (int i = 0;i < MAX_NEXTS;i++) {
			k = (key + (int)pow(2,i)) % MAX_NODE;
			node = (CHnext *)malloc(sizeof(CHnext));
			if (node) {
				node->ip = ip;
				node->port = port;
				this->nexts[i] = node;
			}
			
		}
		
		//constroi a verdadeira tabela de vizinhos
		makeNexts();
		//buscando o nó anterior
		n = findMachine(k);
		if (n) {
			this->prior = n;
		}

		//solicita a divisão de chaves
		CHnext* resp;
		this->numberOfKeys = 0;
		resp = findMachine(this->key);
		if (resp) {
			int resul;
			resul = divisionKeys(resp);
			printf("resultado da divisao de chaves = %d\n",resul);
			if (resul > 0) {
				this->numberOfKeys = resul;
			}
		}
		this->connected = 1;
	}
}

void Chord::createNetwork (char *nodeAddress, short port) {
	if (this->connected) {
		this->disconnect();
	}
	
	this->myIP = ipCharToInt(nodeAddress);
	this->myPort = port;
	this->numberOfKeys = MAX_NODE;
	
	this->prior = (CHnext *)malloc(sizeof(CHnext));
	if (this->prior) {
		this->prior->ip = this->myIP;
		this->prior->port = this->myPort;

		for (int i = 0;i < MAX_NEXTS;i++) {
			this->nexts[i] = (CHnext *)malloc(sizeof(CHnext));
			if (this->nexts[i]) {
				this->nexts[i]->ip = this->myIP;
				this->nexts[i]->port = this->myPort;
			}
		}
		this->connected = 1;		
	}
	
	printf("criou rede\n");
}

void Chord::disconnect ( ) {
	if (this->connected) {
		char* buffer;
		char* tmp;
		//void* t1;
		//void* t2;
		//void* t3;
	
		//t1 = (void *) key;
		//t2 = (void *) numberOfKeys;
		//t3 = (void *) myPort;
		buffer = (char *)malloc(sizeof(char)*10);//reserva 10 bytes (80 bits) para as informacoes
		if (buffer) {
			memset(buffer,0,10);//limpa a memoria
			tmp = buffer;
			memcpy(tmp,&this->key,4);
			tmp = tmp + 4;
			memcpy(tmp,&this->myPort,2);
			tmp = tmp + 2;
			memcpy(tmp,&this->numberOfKeys,4);
			send(COP_DISCONNECT,buffer,10,nexts[0]);
		}
	
		this->connected = 0;
		this->terminar = 1;
	}
}


/**
 * Envia o buffer para o ip informado (abre conexão, acrescenta o cabeçalho e envia
 * os dados)
 * @return int
 * @param	operacao
 * @param	buffer
 * @param	length
 * @param	node nó que receberá a mensagem
 */
int Chord::send (int operacao,void* buffer, int length, CHnext* node) {
	if (node) {
		char* c;
		char* c2;

		c = (char *)malloc(length + CHORDHEADERSIZE);

		if (c) {
			memset(c,0,length + CHORDHEADERSIZE);
			c[0] = (0xF & CHORDVERSION) << 4;//coloca a versão do protocolo
			operacao = 0xF & operacao;//remove qualquer lixo que venha na operacao
			c[0] = c[0] | operacao;
			c2 = c + CHORDHEADERSIZE;
			memcpy(c2,buffer,length);//copia o pacote que deve ser enviado

			tcpSocket socket;
			printf("%d: tentando conectar na porta %d\n",this->numero,node->port);
			if (socket.connect(ipIntToChar(node->ip),node->port) == SOCKET_OK) {
				printf("%d: conectou, enviando os dados, operacao %d\n",this->numero,operacao);
				if( socket.write(c,length + CHORDHEADERSIZE) != SOCKET_OK ){
					cerr<<"Unable to write to socket.\n";
					exit(EXIT_FAILURE);
				} else {
					socket.close();
					printf("%d: dados enviados com sucesso\n",this->numero);
				}
			}
			free(c);
			free(buffer);
		}
	}
	return 0;
}


void Chord::listen() {
	int operacao;
	int versao;
	
	sockServer.listen(this->myPort,50);
	//printf("%d: etapa1 \n",this->numero);
	
	if (sockServer.accepting()) {
		//printf("%d: etapa2\n",this->numero);
		tcpSocket* socket;
		int cabecalho;
		while (true) {
			//printf("%d: etapa3\n",this->numero);
			if (this->terminar) break;
			//printf("%d: etapa4\n",this->numero);
			socket = new tcpSocket();
			if (sockServer.accept(*socket) == SOCKET_OK) {
				//printf("%d: etapa4.1\n",this->numero);
				if (socket->connected()) {
					//printf("%d: etapa5\n",this->numero);
					if (socket->read(&cabecalho,1) == SOCKET_OK) {
						//printf("%d: etapa6\n",this->numero);
						cabecalho = cabecalho & 0xFF;
				
						operacao = cabecalho & 0xF;
						versao = (cabecalho >> 4) & 0xF;
						printf("%d: versao %d operacao %d\n",this->numero,versao,operacao);
				
						//if (operacao == CHORDVERSION) {
							printf("%d: versao de protocolo aceita\n",this->numero);
							switch (operacao) {
								case COP_FIND:
									l_findMachine(*socket);
									break;
								case COP_FINDRESPONSE:
									l_findMachineResponse(*socket);
									break;
								case COP_UPDATEKEY:
									l_updateKeys(*socket);
									break;
								case COP_DIVISIONKEY:
									l_divisionKeys(*socket);
									break;
								case COP_DIVISIONKEYRESPONSE:
									l_divisionKeysResponse(*socket);
									break;
								case COP_DISCONNECT:
									l_disconnect(*socket);
									break;
								case COP_ASKTO:
									l_askTo(*socket);
									break;
							}
						//} else {
						//	printf("%d: versao de protocolo invalida\n",this->numero);
						//}
					}
				}
			}
		}
		printf("%d: fechando socket do servidor\n",this->numero);
		sockServer.close();
	}
	
}

void Chord::findResponse (CHnext* node, CHnext* nodeTarget) {
	char* buffer;
	char* tmp;
	//void* t1;
	//void* t2;
	if (node && nodeTarget) {
		buffer = (char *) malloc(6);
		if (buffer) {
			
			//t1 = (void *) nodeTarget->ip;
			//t2 = (void *) nodeTarget->port;
			
			tmp = buffer;
			memset(buffer,0,6);
			memcpy(tmp,&nodeTarget->ip,4);
			tmp+= 4;
			memcpy(tmp,&nodeTarget->port,2);
			send(COP_FINDRESPONSE,buffer,6,node);
		}
	}
}


/**
 * Implementa a operação 3: Envia uma mensagem para um nó informando que se ele
 * possui alguma chave que aponta para as chaves informadas, que deve atualiza-la
 * em sua tabela.
 * @param	node nó que deverá ter seus nós atualizados
 */
void Chord::sendUpdateKeys (CHnext* node ) {
	char* buffer;
	char* tmp;
	//void* t1;
	//void* t2;
	//void* t3;
	//void* t4;
	
	//t1 = (void *) myIP;
	//t2 = (void *) key;
	//t3 = (void *) numberOfKeys;
	//t4 = (void *) myPort;
	
	buffer = (char *)malloc(14);
	if (buffer) {
		memset(buffer,0,12);
		tmp = buffer;
		memcpy(tmp,&this->myIP,4);
		tmp = tmp + 4;
		memcpy(tmp,&this->myPort,2);
		tmp = tmp + 2;
		memcpy(tmp,&this->key,4);
		tmp = tmp + 4;
		memcpy(tmp,&this->numberOfKeys,4);
		send(COP_UPDATEKEY,buffer,14,node);
	}
}

int Chord::divisionKeys (CHnext* node) {
	char* buffer;
	char* tmp;
	//void* t1;
	//void* t2;
	//void* t3;
	
	//t1 = (void *) myIP;
	//t2 = (void *) myPort;
	//t3 = (void *) key;
	
	buffer = (char *) malloc(10);
	if (buffer) {
		memset(buffer,0,10);
		tmp = buffer;
		memcpy(tmp,&this->myIP,4);
		tmp+= 4;
		memcpy(tmp,&this->myPort,2);
		tmp+=2;
		memcpy(tmp,&this->key,4);
		this->divisionSended = 1;
		send(COP_DIVISIONKEY,buffer,10,node);
		//semDiv->enterCR();
		printf("%d: travando e aguardando resposta a divisao de chaves\n",this->numero);
		pthread_mutex_lock(&this->semDiv);
		printf("%d: recebido resultado da divisao de chaves, valor = %d\n",this->numero,this->tmpNKeys);
		return this->tmpNKeys;
	}
	return NULL;
}

void Chord::divisionKeysResponse (CHnext* node,int numberOfKeys) {
	char* buffer;
	//void* t1;
	
	if (node) {
		buffer = (char *) malloc(4);
		if (buffer) {
			//t1 = (void *) numberOfKeys;
			memset(buffer,0,4);
			memcpy(buffer,&this->numberOfKeys,4);
			send(COP_DIVISIONKEYRESPONSE,buffer,4,node);

		}
	}
}

void Chord::askTo (CHnext* node,CHnext* nodeTarget ) {
	char* buffer;
	char* tmp;
	//void* t1;
	//void* t2;
	if (node && nodeTarget) {
		buffer = (char *) malloc(6);
		if (buffer) {
			//t1 = (void *) nodeTarget->ip;
			//t2 = (void *) nodeTarget->port;
			tmp = buffer;
			memset(buffer,0,6);
			memcpy(tmp,&nodeTarget->ip,4);
			tmp+=4;
			memcpy(tmp,&nodeTarget->port,2);
			send(COP_ASKTO,buffer,6,node);
		}
	}
}


void Chord::l_findMachine(tcpSocket& sock) {
	int ipconsulta;
	short porta;
	int chave;
	
	if (sock.read(&ipconsulta,4) == SOCKET_OK) {
		if (sock.read(&porta,2) == SOCKET_OK) {
			if (sock.read(&chave,4) == SOCKET_OK) {
				printf("%d: pacote de busca de maquina aceito\n",this->numero);
				//avalia se o no é o responsavel pela chave
				int responsavel = 0;
				
				responsavel = inRange(key,numberOfKeys,chave);

				CHnext* no;
				no = (CHnext *) malloc(sizeof(CHnext));
				if (no) {
					no->ip = ipconsulta;
					no->port = porta;
					//da continuidade a busca na rede
					if (!responsavel) {
						sendFindMachine(chave,no,this->nextOfKey(chave));
					} else {
						CHnext* myNode;
						myNode = (CHnext *) malloc(sizeof(CHnext));
						if (myNode) {
							myNode->ip = myIP;
							myNode->port = myPort;
							//envia uma mensagem informando que é o responsável
							findResponse(no,myNode);
							free(myNode);
						}
					}
				}
			
			}
		}
	}
	sock.close();
}

void Chord::l_findMachineResponse(tcpSocket& sock){
	int ip;
	short porta;
	if (sock.read(&ip,4) == SOCKET_OK) {
		if (sock.read(&porta,2) == SOCKET_OK) {
			printf("%d: pacote de resposta de busca aceito\n",this->numero);
			CHnext* no;
			no = (CHnext *) malloc(sizeof(CHnext));
			if (no) {
				no->ip = ip;
				no->port = porta;
				this->tmpNode = no;
				//semFind->leaveCR();
				//printf("achou máquina, porta: %d\n",porta);
				pthread_mutex_unlock(&this->semFind);
			}
		}
	}
	sock.close();
}

void Chord::l_updateKeys(tcpSocket& sock){
	int ip;
	short porta;
	int chave;
	int qtdchaves;
	if (sock.read(&ip,4) == SOCKET_OK) {
		if (sock.read(&porta,2) == SOCKET_OK) {
			if (sock.read(&chave,4) == SOCKET_OK) {
				if (sock.read(&qtdchaves,4) == SOCKET_OK) {
					printf("%d: pacote de atualização de chaves aceito\n",this->numero);
					int atualizar,k;
					for (int i = 0;i < MAX_NEXTS;i++) {
						k = (key + (int)pow(2,i)) % MAX_NODE;
						atualizar = inRange(chave,qtdchaves,k);

						if (atualizar) {
							free(this->nexts[i]);
							CHnext* no;
							no = (CHnext *) malloc(sizeof(CHnext));
							if (no) {
								no->ip = ip;
								no->port = porta;
								//if (this->nexts[i]) {
								//	free(this->nexts[i]);
								//}
								this->nexts[i] = no;
							}
						}

					}
				}
			}
		}
	}
	sock.close();
}

void Chord::l_divisionKeys(tcpSocket& sock){
	int iporigem,chave;
	short porta;
	
	if (sock.read(&iporigem,4) == SOCKET_OK) {
		if (sock.read(&porta,2) == SOCKET_OK) {
			if (sock.read(&chave,4) == SOCKET_OK) {
				printf("%d: pacote de busca de divisão de chaves\n",this->numero);
				CHnext* no;
				no = (CHnext *) malloc(sizeof(CHnext));
				if (no) {
					no->ip = iporigem;
					no->port = porta;
					
					if (inRange(this->key,this->numberOfKeys,chave)) {
						int k = this->key, k2 = chave;
						if (chave > this->key) {
							int tmp = MAX_NODE - chave;
							k = k + tmp;
							k2 = 0;
						}
						int tmp = (k - k2);
						int sendN = (this->numberOfKeys - tmp);
						
						printf("%d: enviando valor %d como qtd de chaves\n",this->numero,sendN);
						
						this->numberOfKeys = tmp - 1;
						divisionKeysResponse (no,sendN);
						
					} else {
						this->askTo(no,this->prior);
					}
				}
			}
		}
	}
	sock.close();
}

void Chord::l_divisionKeysResponse(tcpSocket& sock){
	int nk;
	if (sock.read(&nk,4) == SOCKET_OK) {
		if (this->divisionSended) {
			printf("%d: buffer: %d\n",this->numero,this->tmpNKeys);
			printf("%d: pacote de resposta a divisao de chaves aceito, qtd chaves %d\n",this->numero,nk);
			this->divisionSended = 0;
			this->tmpNKeys = nk;
			pthread_mutex_unlock(&this->semDiv);
		}
	}
	sock.close();
}

void Chord::l_disconnect(tcpSocket& sock){
	int chavesaindo,nk;
	short porta;
	if (sock.read(&chavesaindo,4) == SOCKET_OK) {
		if (sock.read(&porta,2) == SOCKET_OK) {
			if (sock.read(&nk,4) == SOCKET_OK) {
				printf("%d: pacote de desconexão aceito\n",this->numero);
				//aceita esse pedido apenas do seu vizinho anterior
				//if (ip == this->prior->ip) {
				int novNumberOfKeys = nk + this->key - chavesaindo;
				this->numberOfKeys = novNumberOfKeys;
				
				
				//atualizar a lista dos interessados
				//}
			}
		}
	}
	sock.close();
}

void Chord::l_askTo(tcpSocket& sock){
	int novoIp;
	short porta;
	if (sock.read(&novoIp,4) == SOCKET_OK) {
		if (sock.read(&porta,2) == SOCKET_OK) {
			if (this->divisionSended) {
				printf("%d: pacote de Pergunte a outra máquina aceito\n",this->numero);
				CHnext* no;
				no = (CHnext *)malloc(sizeof(CHnext));
				if (no) {
					no->ip = novoIp;
					no->port = porta;
					divisionKeys (no);
					free(no);
				}
			}
		}
	}
	sock.close();
}



char* Chord::ipIntToChar(int ipAddress) {

	char* ip;
	char* p;
	char* tmp;
	int blocos = 4;
	tmp = (char *)malloc(sizeof(char)*blocos);
	ip = (char *)malloc(sizeof(char)*IP_MAX_SIZE);
	int byte;
	p = ip;
	for (int i = 0;i < blocos;i++) {
		
		byte = (ipAddress>>((blocos-1)-i)*8) & 0xFF;		
		
		sprintf(tmp,"%d",byte);
		
		strcpy(p,tmp);
		
		p = p + strlen(tmp);

		if ((i + 1) < blocos) {
			strcpy(p,".");
			p = p + 1;
		}
	}
	
	free(tmp);
	
	return ip;

}

int Chord::ipCharToInt(char* charIp) {
	char* pch;
	char ipBuffer[IP_MAX_SIZE+1];
	int ip, intByte;
	bool error = false;

	ip &= 0x00000000;
	strncpy(ipBuffer,charIp,IP_MAX_SIZE);
	pch = strtok(ipBuffer,".");
	if( pch == NULL ) exit(0); 
	for( int i=0 ; i<4 ; i++ ){
		if( pch != NULL  ){
			intByte = atoi(pch);
			if( intByte >= 0 && intByte <= 255 ){
				ip = ip | atoi(pch);
				if( i != 3 ) ip = ip<<8;
				pch = strtok(NULL,".");
			}
			else{ error = true; break; }
		}
		else{ error = true; break; }
	}
	if( error ){
		printf("Erro on ipCharToInt convertion. Maybe the ip has incorrect value.\n");
		exit(EXIT_FAILURE);
	}
	return ip;
}

//GETS AND SETS

CHnext* Chord::nextOfKey(int key) {
	if (this->connected) {
		//printf("testeNext\n");
		return this->nexts[0];
	} else {
		return this->prior;
	}
}

/**
 * Set the value of m_prior
 * Máquina responsável pelo nó anterior
 * @param new_var the new value of m_prior
 */
void Chord::setPrior ( CHnext* new_var ) {
	prior = new_var;
}

/**
 * Get the value of m_prior
 * Máquina responsável pelo nó anterior
 * @return the value of prior
 */
CHnext* Chord::getPrior ( ) {
	return prior;
}

/**
 * Set the value of numberOfKeys
 * Quantidade de chaves que o nó é responsável além da que ele tem obrigação
 * @param new_var the new value of numberOfKeys
 */
void Chord::setNumberOfKeys (int new_var ) {
	numberOfKeys = new_var;
}

/**
 * Get the value of numberOfKeys
 * Quantidade de chaves que o nó é responsável além da que ele tem obrigação
 * @return the value of numberOfKeys
 */
int Chord::getNumberOfKeys ( ) {
	return numberOfKeys;
}

/**
 * Set the value of sockServer
 * Armazena o socket de servidor para a rede Chord
 * @param new_var the new value of sockServer
 */
void Chord::setSockServer ( tcpSocketServidor new_var ) {
	sockServer = new_var;
}

/**
 * Get the value of sockServer
 * Armazena o socket de servidor para a rede Chord
 * @return the value of sockServer
 */
tcpSocketServidor Chord::getSockServer ( ) {
	return sockServer;
}

/**
 * Set the value of key
 * Chave que o nó é responsável
 * @param new_var the new value of key
 */
void Chord::setKey (int new_var ) {
	key = new_var;
}

/**
 * Get the value of key
 * Chave que o nó é responsável
 * @return the value of key
 */
int Chord::getKey ( ) {
	return key;
}

bool Chord::inRange(int nodeKey,int numberOfKey,int keyAnswer) {
	int min = (key - numberOfKeys);
	printf("key = %d, numberOfKeys = %d, keyAnswer = %d, min = %d, MAX_NODE = %d\n",key,numberOfKeys,keyAnswer,min,(int)MAX_NODE);
	if (keyAnswer < nodeKey) {
		return (keyAnswer >= min);
	} else {
		if (min < 0) {
			return (keyAnswer >= (MAX_NODE + min));
		}
	}
	return false;
}


